import OS from "os";

import ChildProcess from "child_process";
const Exec = ChildProcess.exec;

import Express      from "express";
import cookieParser from "cookie-parser";
import bodyParser   from "body-parser";

import PomeloLogger from "pomelo-logger";
const Logger = PomeloLogger.getLogger("Master", process.pid, __filename);

import GamePlugin   from "./plugin/gamePlugin";

const GlobalConfig = require("../config/global.config.json");
const MasterConfig = require("../config/master.config.json");

class Master extends GamePlugin {
    constructor(){
        super("Master");

        this._host            = MasterConfig.host;
        this._port            = MasterConfig.master.port;
        this.gmServer         = null;

        this._debugPort       = GlobalConfig.debug.addPort + this._port;
        this._debugMode       = GlobalConfig.debug.open;

        this._monitorPort     = GlobalConfig.monitor.addPort + this._port;
        this._monitorInterval = GlobalConfig.monitor.interval;
        this.monitorServer    = null;
        this.serverStats      = {};

        this._currentPort     = MasterConfig.child.startPort;
        this.socketServers    = {};
    }

    configure() {
        if (!this.isStatus("CREATED")) return;
        this.setStatus("INITIALIZING");

        this.setStatus("INITIALIZED");
    }
    start() {
        if (this.isStatus("CREATED")) this.configure();

        if (!this.isStatus("INITIALIZED")) return;
        this.setStatus("STARTING");

        this.gmServer = new Express();
        this.gmServer.use(bodyParser.json());
        this.gmServer.use(bodyParser.urlencoded({ extended: false }));
        this.gmServer.use(cookieParser());

        this.gmServer.all("*", (req, res, next) => {
            Logger.info("[ %s ] Request [ %j ] With Args [ %s ]", req.method, req.headers, req.url);

            if (!this._validUserPassword(req.body.username, req.body.password)) return res.json({});

            next();
        });

        this.gmServer.post("/startNServer", (req, res) => {
            var n = parseInt(req.body.n, 10);
            var ports = [];
            if (n > 0) ports = this.startNServer(n);

            res.json({"ports": ports});
        });
        this.gmServer.post("/startServer", (req, res) => {
            var port = parseInt(req.body.port, 10);
            if (port > 0 && port < 65535) this.startServer(port);
            res.json({});
        });
        this.gmServer.post("/killAllServer", (req, res) => {
            var ports = this.killAllChild();
            res.json({"ports": ports});
        });
        this.gmServer.post("/killServer", (req, res) => {
            var port = parseInt(req.body.port, 10);
            if (port > 0 && port < 65535) this.killChild(port);
            res.json({});
        });
        this.gmServer.post("/restartServer", (req, res) => {
            var port = parseInt(req.body.port, 10);
            if (port > 0 && port < 65535) this.restartServer(port);
            res.json({});
        });

        this.gmServer.use(function(req, res, next) {
            var err = new Error('Not Found');
            err.status = 404;
            next(err);
        });

        this.gmServer.use(function(err, req, res) {
            res.locals.message = err.message;
            res.locals.error = req.app.get('env') === 'development' ? err : {};

            res.status(err.status || 500);
            res.json(res.locals);
        });

        function httpServerOnListen(err) {
            Logger.info("Loaded Master Server On Port [ %s ]", this._port, err);
            this.setStatus("STARTED");
        }
        this.gmServer.listen(this._port, httpServerOnListen.bind(this));

        this.startMonitor();
    }
    _validUserPassword(username, password) {
        return (username === "Justin" && password === "123456");
    }
    startMonitor() {
        // Do not start the server if the port is invalid
        this.monitorServer = new Express();
        this.monitorServer.use(bodyParser.json());
        this.monitorServer.use(bodyParser.urlencoded({ extended: false }));
        this.monitorServer.use(cookieParser());

        this.monitorServer.get("/", (req, res) => {
            Logger.info("[ %s ] Request [ %j ] With Args [ %s ]", req.method, req.headers, req.url);
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.json(this.serverStats);
        });

        this.monitorServer.use(function(req, res, next) {
            var err = new Error('Not Found');
            err.status = 404;
            next(err);
        });

        this.monitorServer.use(function(err, req, res) {
            res.locals.message = err.message;
            res.locals.error = req.app.get('env') === 'development' ? err : {};

            res.status(err.status || 500);
            res.json(res.locals);
        });

        function httpServerOnListen(err) {
            Logger.info("Loaded stats server on port [ %s ]", this._monitorPort, err);
            setInterval(this.updateMonitor.bind(this), this._monitorInterval * 1000);
        }
        this.monitorServer.listen(this._monitorPort, httpServerOnListen.bind(this));
    }

    update() {
        setTimeout(this.update.bind(this), 1);
        if (!this.isStatus("RUNNING")) return;


    }
    updateMonitor () {
        this.serverStats.ports = [];
        Object.keys(this.socketServers).forEach(port => {
            this.serverStats.ports.push(port);
        });
    }

    stop() {
        if (!this.isStatus("STARTED")) return;
        this.setStatus("STOPPING");

        this.setStatus("STOPPED");
    }

    _getNewServerPort() {
        this._currentPort += 1;
        if (this._currentPort > MasterConfig.child.endPort) {
            Logger.warn("Server Available Port Are All Used Out!");
            return -1;
        }
        return this._currentPort;
    }

    _createNewServer(port, debugInfo) {
        var server;

        if (this._debugMode && debugInfo) {
            server = ChildProcess.fork(__dirname + "/bin/serverIns.js", [port, port, this._host], {execArgv: [debugInfo]});
        } else {
            server = ChildProcess.fork(__dirname + "/bin/serverIns.js", [port, port, this._host]);
        }

        this.socketServers[port] = server;
        server.on("message", (msg) => {
            Logger.info("Master Receive Message [ %j ] From Pid [ %s ]", msg, server.pid);
        });
        server.on("exit", ()=>{
            this.socketServers[port] = null;
            delete this.socketServers[port];
            Logger.warn("Server [ %s:%s ] Exit!", this._host, port);
        })
    }

    killChild(port) {
        if (!this.socketServers[port]) return;

        this.socketServers[port].send("kill");
    }
    killAllChild() {
        var ports = [];
        Object.keys(this.socketServers).forEach(port => {
            this.killChild(port);
            ports.push(port);
        });
        return ports;
    }

    startServer(port) {
        if (this.socketServers[port]) return;

        let debugInfo = "--debug=" + this._debugPort;
        this._createNewServer(port, debugInfo);
        Logger.info("Server [ %s:%s ] Start With Args [ %j ]", this._host, port, debugInfo);
    }
    startNServer(n) {
        var ports = [];
        for (let i=0; i<n; i++) {
            let port = this._getNewServerPort();
            ports.push(port);
            this.startServer(port);
        }
        return ports;
    }

    restartServer(port) {
        var debugInfo = "--debug=" + this._debugPort;
        if (this.socketServers[port]) {
            this.killChild(port);
            setTimeout(function (port) {
                this._createNewServer(port, debugInfo);
                Logger.info("Server [ %s:%s ] Restart With Args [ %j ]", this._host, port, debugInfo);
            }.bind(this), 2000, port);
        } else {
            this._createNewServer(port, debugInfo);
            Logger.info("Server [ %s:%s ] Restart With Args [ %j ]", this._host, port, debugInfo);
        }
    }
}

module.exports = Master;
export default Master;
